#include <iostream>
#include <vector>
#include <algorithm>

// OI/ACM风格常用宏定义和快读
#define FAST_IO std::ios_base::sync_with_stdio(false); std::cin.tie(NULL);
#define FOR(i, start, end) for (int i = (start); i < (end); ++i)

// 为了代码简洁，使用pair表示坐标
using pii = std::pair<int, int>;

int T, n, m;
std::vector<std::vector<int>> a;
std::vector<pii> pos; // pos[v] = {row, col} 存储稀有度为v的坐标

// 检查是否存在一条路径，其开心程度至少为k
// 即，是否存在一条路径，收集了所有稀有度 < k 的宝物
bool check(int k) {
    if (k == 0) { // 开心值为0总是可以达成的（除非n=m=1且a[0][0]=0，但题目定义d=1）
        return true;
    }
    if (k > n * m) { // 不可能达成比n*m还大的开心值
        return false;
    }

    std::vector<pii> must_visit;
    // 收集所有必须访问的点的坐标（稀有度 0, 1, ..., k-1）
    for (int i = 0; i < k; ++i) {
        must_visit.push_back(pos[i]);
    }

    // 检查起点(0,0)是否在必须访问的列表中
    // 如果a[0][0]的稀有度 >= k，则 (0,0) 不在must_visit中，需要手动加入
    if (a[0][0] >= k) {
        must_visit.push_back({0, 0});
    }
    // 同样，检查终点(n-1,m-1)
    if (a[n - 1][m - 1] >= k) {
        must_visit.push_back({n - 1, m - 1});
    }

    // 按照坐标排序：行优先，列其次
    // 这是路径在网格中前进的自然顺序
    std::sort(must_visit.begin(), must_visit.end());

    // 移除因重复添加(0,0)或(n-1,m-1)而产生的重复点
    must_visit.erase(std::unique(must_visit.begin(), must_visit.end()), must_visit.end());


    // 检查排序后的点集是否构成一个合法的路径片段序列
    // 即 p_i 的坐标必须不大于 p_{i+1} 的坐标
    for (size_t i = 0; i + 1 < must_visit.size(); ++i) {
        pii current = must_visit[i];
        pii next = must_visit[i + 1];
        if (current.first > next.first || current.second > next.second) {
            // 如果后一个点的行或列小于前一个点，则无法从 current 到达 next
            return false;
        }
    }
    
    // 还需要确保路径的起点和终点是(0,0)和(n-1,m-1)
    if (must_visit.empty() || must_visit.front() != pii{0, 0} || must_visit.back() != pii{n - 1, m - 1}) {
        // 如果排序后，起点不是(0,0)或终点不是(n-1,m-1)，则路径不完整
        return false;
    }

    return true;
}


void solve() {
    std::cin >> n >> m;
    a.assign(n, std::vector<int>(m));
    pos.assign(n * m, {0, 0});

    // 读入数据并预处理pos数组
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            std::cin >> a[i][j];
            pos[a[i][j]] = {i, j};
        }
    }

    // 二分搜索答案
    int low = 0, high = n * m, ans = 0;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (check(mid)) {
            // 如果check(mid)成功，说明开心程度mid是可以达到的
            // 我们尝试寻求更高的开心程度
            ans = mid;
            low = mid + 1;
        } else {
            // 如果check(mid)失败，说明开心程度mid无法达到
            // 必须降低目标
            high = mid - 1;
        }
    }

    std::cout << ans << "\n";
}

int main() {
    // 题目输入为文件，实际比赛中需要取消注释
    // freopen("maze.in", "r", stdin);
    // freopen("maze.out", "w", stdout);
    std ::ios_base ::sync_with_stdio(false);
    std ::cin.tie(0);
    std::cin >> T;
    while (T--) {
        solve();
    }
}
